package xyz.lisys.boot.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.permission.PermissionResolver;
import org.apache.shiro.authz.permission.RolePermissionResolver;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.config.ShiroFilterConfiguration;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import xyz.lisys.boot.util.ResponseUtil;

import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Configuration
@ConditionalOnProperty(name = "shiro.enabled", havingValue = "true")
public class ShiroConfigurer {

    @Value("#{ @environment['shiro.loginUrl'] ?: '/login.jsp' }")
    protected String loginUrl;

    @Value("#{ @environment['shiro.successUrl'] ?: '/' }")
    protected String successUrl;

    @Value("#{ @environment['shiro.unauthorizedUrl'] ?: null }")
    protected String unauthorizedUrl;

    @Bean
    protected Authorizer authorizer(
            @Autowired(required = false) PermissionResolver permissionResolver,
            @Autowired(required = false) RolePermissionResolver rolePermissionResolver
    ) {
        ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();

        if (permissionResolver != null) {
            authorizer.setPermissionResolver(permissionResolver);
        }

        if (rolePermissionResolver != null) {
            authorizer.setRolePermissionResolver(rolePermissionResolver);
        }

        return authorizer;
    }

    /**
     * Filter Name	Class
     * anon	        org.apache.shiro.web.filter.authc.AnonymousFilter
     * authc	    org.apache.shiro.web.filter.authc.FormAuthenticationFilter
     * authcBasic	org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
     * perms	    org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
     * port	        org.apache.shiro.web.filter.authz.PortFilter
     * rest	        org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
     * roles	    org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
     * ssl	        org.apache.shiro.web.filter.authz.SslFilter
     * user	        org.apache.shiro.web.filter.authc.UserFilter
     */
    @Bean
    protected ShiroFilterFactoryBean shiroFilterFactoryBean(
            SecurityManager securityManager,
            @Autowired(required = false) Map<String, Filter> filterMap
    ) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        ShiroFilterConfiguration shiroFilterConfiguration = new ShiroFilterConfiguration();
        DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();

        defaultShiroFilterChainDefinition.addPathDefinition("/auth/**", "anon");
        defaultShiroFilterChainDefinition.addPathDefinition("/apollo", "anon");
        defaultShiroFilterChainDefinition.addPathDefinition("/**", "authc");

        filterMap = Optional.ofNullable(filterMap).orElseGet(HashMap::new);
        filterMap.put("authc", new AuthcFormAuthenticationFilter());

        filterFactoryBean.setLoginUrl(loginUrl);
        filterFactoryBean.setSuccessUrl(successUrl);
        filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

        filterFactoryBean.setSecurityManager(securityManager);
        filterFactoryBean.setShiroFilterConfiguration(shiroFilterConfiguration);
        filterFactoryBean.setGlobalFilters(Collections.singletonList(DefaultFilter.invalidRequest.name()));
        filterFactoryBean.setFilterChainDefinitionMap(defaultShiroFilterChainDefinition.getFilterChainMap());
        filterFactoryBean.setFilters(filterMap);

        return filterFactoryBean;
    }

    @Slf4j
    @Component
    public static class CustomerRealm extends AuthorizingRealm {

        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            String principal = principalCollection.getPrimaryPrincipal().toString();

            log.info("用户授权信息传递");
            log.info(principalCollection.getPrimaryPrincipal().toString());

            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.addRole("user");

            return simpleAuthorizationInfo;
        }

        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            log.info("用户登录信息传递");
            log.info(authenticationToken.getPrincipal().toString());
            log.info(authenticationToken.getCredentials().toString());

            return new SimpleAuthenticationInfo(
                    authenticationToken.getPrincipal(),
                    "password",
                    getName()
            );
        }
    }

    @Slf4j
    public static class AuthcFormAuthenticationFilter extends FormAuthenticationFilter {

        @Override
        protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
            saveRequest(request);
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setStatus(400);
            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter out = httpServletResponse.getWriter();
            val objectMapper = new ObjectMapper();
            out.println(objectMapper.writeValueAsString(ResponseUtil.error("验证失败")));
            out.flush();
            out.close();
        }

    }
}
